package edu.weseda.simulator;

import java.util.List;
import java.util.Random;

import Jama.Matrix;


public class PSO {

	private int iteration;			//iterate times
	private int particleNum;		//particle number
	private double particleMax;		//maximal value of particle
	private double particleMin;		//minimal value of particle
	private double beta;			
	private double wmax;
	private double wmin;
	private Matrix particles;
	private Matrix velocities;
	private double[] pBestP;		//partial best particle
	private double[] gBestP;		//global best particle
	private double gBestC;			//global best cost
	
	public PSO(){
		this.iteration = 50;
		this.particleNum = 50;
		this.particleMax = 1;
		this.particleMin = 0;
		this.beta = 0.5;
		this.wmax = 0.9;
		this.wmin = 0.4;
	}
	
	
	public int getIteration() {
		return iteration;
	}
	
	public void setIteration(int iteration) {
		this.iteration = iteration;
	}
	
	public int getParticleNum() {
		return particleNum;
	}
	
	public void setParticleNum(int particleNum) {
		this.particleNum = particleNum;
	}
	
	public double getParticleMax() {
		return particleMax;
	}
	
	public void setParticleMax(double particleMax) {
		this.particleMax = particleMax;
	}
	
	public double getParticleMin() {
		return particleMin;
	}
	
	public void setParticleMin(double particleMin) {
		this.particleMin = particleMin;
	}
	
	public double getBeta() {
		return beta;
	}
	
	public void setBeta(double beta) {
		this.beta = beta;
	}
	
	public double getWmax() {
		return wmax;
	}
	
	public void setWmax(double wmax) {
		this.wmax = wmax;
	}
	
	public double getWmin() {
		return wmin;
	}
	
	public void setWmin(double wmin) {
		this.wmin = wmin;
	}
	
	public PSOResult pso(Matrix coefficient, List<Sensor> sensorList){
		initialize(coefficient, sensorList);
		int sensorAmount = coefficient.getRowDimension();
		int lampAmount = coefficient.getColumnDimension();
		for(int i=0;i<iteration;i++){
			double velMax = (1.0 - i/iteration)*(particleMax - particleMin)/2.0;
			for(int j=0;j<particleNum;j++){
				double w_rand = wmax - (wmax - wmin)/iteration*i;
				double c1 = 0.8 - 0.5*Math.random();
			    double c2 = 0.8 - 0.5*Math.random();
			    double r1 = new Random().nextInt(2);
			    double r2 = new Random().nextInt(2);
			    double[] estimation = new double[sensorAmount];
			    double bestCost = -1;
			    int bestIndex = -1;
			     for(int k=0;k<lampAmount;k++){
			    	 double vel = w_rand*velocities.get(j, k) 
			    			 + c1*r1*(pBestP[k] - particles.get(j, k)) + c2*r2*(gBestP[k] - particles.get(j, k));
			    	 if(vel > velMax){
			    		 vel = velMax;
			    	 }
			    	 else if(vel < -velMax){
			    		 vel = -velMax;
			    	 }
			    	 velocities.set(j, k, vel);
			    	 double par = particles.get(j, k) + vel;
			    	 if(par < 0.2){
			    		 par = 0;
			    	 }
			    	 if(par > particleMax){
			    		 par = particleMax;
			    	 }
			    	 particles.set(j, k, par);
			     }
			     double est = 0;
			     for(int k=0;k<sensorAmount;k++){
			    	 for(int l=0;l<lampAmount;l++){
						est = coefficient.get(k, l)*particles.get(j, l);
			    	 }
					estimation[j] += est;
			     }
			     double co = 0;
			     for(int k=0;k<sensorAmount;k++){
			    	 double penalty = getPenalty(estimation[k], sensorList.get(k).getTarget());
			    	 co += penalty*Math.abs((estimation[k] - sensorList.get(k).getTarget()));
			     }
			     if(bestCost < 0 || bestCost > co){
			    	 bestCost = co;
			    	 bestIndex = i;
			     }						     
			     pBestP = particles.getArrayCopy()[bestIndex];
			     if(bestCost < gBestC){
					gBestP = particles.getArrayCopy()[bestIndex];
					gBestC = bestCost;
			     }
			}
		}
		PSOResult result = new PSOResult();
		Matrix lampConfig = new Matrix(gBestP,1);
		double energySavingProportion = sum(lampConfig.getRowPackedCopy())/lampConfig.getRowPackedCopy().length;
		result.setLampConfig(lampConfig);
		result.setEnergySavingProportion(energySavingProportion);
		return result;
	}
	
	private double sum(double[] input){
		double result = 0;
		for(int i=0;i<input.length;i++){
			result += input[i];
		}
		return result;
	}
	
	// initialize the particles and velocities
	private void initialize(Matrix coefficient, List<Sensor> sensorList){
		int sensorAmount = coefficient.getRowDimension();
		int lampAmount = coefficient.getColumnDimension();
		double[] estimation = new double[sensorAmount];
		double[] cost = new double[particleNum];
		double bestCost = -1;
		int bestIndex = -1;
		particles = new Matrix(particleNum,lampAmount);
		velocities = new Matrix(particleNum,lampAmount);
		for(int i=0;i<particleNum;i++){
			for(int j=0;j<lampAmount;j++){
				particles.set(i, j, Math.random());
				if(particles.get(i, j) < 0.2){
					particles.set(i, j, 0);
				}
				if(particles.get(i, j) > 0.9999){
					particles.set(i, j, 1);
				}
				velocities.set(i, j, (Math.random()*-particleMax)*beta);
			}
			double est = 0;
			for(int j=0;j<sensorAmount;j++){
				for(int k=0;k<lampAmount;k++){
					est = coefficient.get(j, k)*particles.get(i, k);
				}
				estimation[i] += est;
			}
			double co = 0;
			for(int j=0;j<sensorAmount;j++){
				double penalty = getPenalty(estimation[j], sensorList.get(j).getTarget());
				co += penalty*Math.abs((estimation[j] - sensorList.get(j).getTarget()));
			}
			if(bestCost < 0 || bestCost > co){
				bestCost = co;
				bestIndex = i;
			}			
			cost[i] = co;
		}
		pBestP = particles.getArrayCopy()[bestIndex];
		gBestP = particles.getArrayCopy()[bestIndex];
		gBestC = bestCost;
	}
	
	private double getPenalty(double estimation, double target){
		if(estimation < target){
			return 50;
		}
		else{
			return 1;
		}
	}

	public PSOResult groupedPSO(Matrix coefficient, List<Sensor> sensorList, int groupNum){
		//divide lamps into groups
		int sensorAmount = coefficient.getRowDimension();
		int lampAmount = coefficient.getColumnDimension();
		double[] imp = calculateImportance(coefficient, sensorList);
		//TODO
		
		return null;
	}
	
	private double[] calculateImportance(Matrix coefficient, List<Sensor> sensorList){
		int sensorAmount = coefficient.getRowDimension();
		int lampAmount = coefficient.getColumnDimension();
		double averageTarget = 0;
		//calculate average target
		for(Sensor sensor : sensorList){
			averageTarget += sensor.getTarget();
		}
		averageTarget = averageTarget / sensorAmount;
		
		double[] lampImportance = new double[lampAmount];
		double[] sensorImportance = new double[sensorAmount];
		//importance of lamp is the sum of sensor importance
		for(int i=0;i<lampAmount;i++){
			for(int j=0;j<sensorAmount;j++){
				sensorImportance[j] = sensorList.get(j).getTarget() / averageTarget;
				lampImportance[i] += coefficient.get(j, i) * sensorImportance[j];
			}
		}
		return lampImportance;
	}
	
	public class PSOResult{
		private Matrix lampConfig;
		private double energySavingProportion;
		
		public Matrix getLampConfig() {
			return lampConfig;
		}
		
		public void setLampConfig(Matrix lampConfig) {
			this.lampConfig = lampConfig;
		}
		
		public double getEnergySavingProportion() {
			return energySavingProportion;
		}
		
		public void setEnergySavingProportion(double energySavingProportion) {
			this.energySavingProportion = energySavingProportion;
		}
	}
	
	
	
	
}

